#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sys/time.h>

#include "dtk_timeline.h"
#include "dtk_debug.h"

DTKTimeLine* dtk_timeline_create (DUInt msecs)
{
    DTKTimeLine* timeline;

    timeline = (DTKTimeLine*)dtk_object_init(sizeof(DTKTimeLine));
    timeline->duration = msecs;
    dtk_list_init(DLIST(timeline), (DPointer*)timeline);
    dtk_link_init(DLINK(timeline), (DPointer*)timeline);

    dtk_signal_register(timeline, "new_frame");
    dtk_signal_register(timeline, "key_frame");

    return timeline;
}

void dtk_timeline_release (DTKTimeLine* self)
{
    if (self == NULL)
    {
        return;
    }

    dtk_object_finalize((DTKObject*)self);
    return;
}

void dtk_timeline_set_duration (DTKTimeLine* self, DUInt msecs)
{
    DTK_ASSERT (self != NULL);
    self->duration = msecs;
    return;
}

DUInt dtk_timeline_get_duration (DTKTimeLine* self)
{
    DTK_ASSERT (self != NULL);
    return self->duration;
}

void dtk_timeline_set_loop (DTKTimeLine* self, DBoolean loop)
{
    DTK_ASSERT (self != NULL);
    self->loop = loop;
    return;
}

DBoolean dtk_timeline_get_loop (DTKTimeLine* self)
{
    DTK_ASSERT (self != NULL);
    return self->loop;
}

void dtk_timeline_set_delay (DTKTimeLine* self, DUInt msecs)
{
    DTK_ASSERT (self != NULL);
    self->delay = msecs;
    return;
}

DUInt dtk_timeline_get_delay (DTKTimeLine* self)
{
    DTK_ASSERT (self != NULL);
    return self->delay;
}

DUInt dtk_timeline_get_past_time (DTKTimeLine* self)
{
    DTK_ASSERT (self != NULL);
    return self->past_time;
}

static DBoolean get_keytime(DList* list, DLink* link, va_list args)
{
    DUInt msecs;
    DTKKeytime* keytime;
    DTKKeytime** ret_keytime;

    keytime = (DTKKeytime*) DLINKDATA(link);

    msecs = va_arg(args, DUInt);
    if (keytime->key_time == msecs)
    {
        *ret_keytime = keytime;
        return DTK_TRUE;
    }
    return DTK_FALSE;
}

static DTKKeytime* timeline_get_keytime (DTKTimeLine* self, DUInt msecs)
{
    DTKKeytime* keytime = NULL;
    DBoolean ret;

    DTK_ASSERT(self != NULL);

    ret = dtk_list_foreach(DLIST(self), get_keytime, msecs, &keytime);
    if (ret == DTK_TRUE && keytime != NULL)
    {
        return keytime;
    }

    return NULL;
}


static DBoolean add_keytime_cb(DList* list, DLink* link, va_list args)
{
    DTKKeytime* keytime = (DTKKeytime*) DLINKDATA(link);
    DTKKeytime* new_keytime;

    new_keytime = va_arg(args, DTKKeytime*);

    if (new_keytime->key_time < keytime->key_time)
    {
        dtk_list_insert_before(list, DLINK(keytime), DLINK(new_keytime));
        return DTK_TRUE;
    }
    return DTK_FALSE;
}

static DBoolean set_ktfunc_cb(DList* list, DLink* link, va_list args)
{
    DTKKeytimeCallback func;
    DPointer usrdata;
    KeytimeFuncs* ktfuncs = (KeytimeFuncs*)DLINKDATA(link);

    func = va_arg(args, DTKKeytimeCallback);
    usrdata = va_arg(args, DPointer);

    if (ktfuncs->func == func)
    {
        ktfuncs->usrdata = usrdata;
        return DTK_TRUE;
    }
    
    return DTK_FALSE;
}

void dtk_timeline_set_keytime (DTKTimeLine* self, DUInt msecs,
    DTKKeytimeCallback func, DPointer usrdata)
{
    KeytimeFuncs* ktfuncs;
    DTKKeytime* keytime;
    DBoolean ret;
    DTK_ASSERT (self != NULL);

    if (msecs >= self->duration)
    {
        return;
    }

    keytime = timeline_get_keytime (self, msecs);

    if (keytime == NULL)
    {
        keytime = (DTKKeytime*)malloc(sizeof(DTKKeytime));
        memset (keytime, 0, sizeof(DTKKeytime));
        keytime->key_time = msecs;

        ret = dtk_list_foreach(DLIST(keytime), add_keytime_cb, keytime);
        if (ret == DTK_FALSE)
        {
            dtk_list_append(DLIST(keytime), DLINK(keytime));
        }
    }
    else
    {
        ret = dtk_list_foreach(DLIST(keytime), set_ktfunc_cb, func, usrdata);
        if (ret == DTK_TRUE)
        {
            return;
        }
    }

    ktfuncs = (KeytimeFuncs*)malloc(sizeof(KeytimeFuncs));
    memset (ktfuncs, 0, sizeof(KeytimeFuncs));
    ktfuncs->func = func;
    ktfuncs->usrdata = usrdata;
    dtk_list_append(DLIST(keytime), DLINK(ktfuncs));


    return;
}

void dtk_timeline_start(DTKTimeLine* self)
{
    DTK_ASSERT (self != NULL);

    if (self->is_playing || self->duration <= 0)
    {
        return;
    }
    
    dtk_mc_add_timeline(dtk_get_default_master_clock(), self);
    self->is_playing = DTK_TRUE;
    
    return;
}

void dtk_timeline_pause(DTKTimeLine* self)
{
    return;
}

void dtk_timeline_stop(DTKTimeLine* self)
{
    dtk_mc_remove_timeline(dtk_get_default_master_clock(), self);
    self->is_playing = DTK_FALSE;
    return;
}

void dtk_timeline_rewind(DTKTimeLine* self)
{
    return;
}

void dtk_timeline_skip(DTKTimeLine* self)
{
    return;
}

DBoolean dtk_timeline_is_playing(DTKTimeLine* self)
{
    DTK_ASSERT (self != NULL);
    return self->is_playing;
}

void dtk_timeline_do_tick (DTKTimeLine *self, struct timeval* tick_time)
{
    DTK_ASSERT (self != NULL);

    if (self->waiting_first_tick == DTK_FALSE)
    {
        self->start_frame_time = *tick_time;
        self->past_time = 0;
        self->waiting_first_tick = DTK_TRUE;
    }
    else
    {
        DUInt64 delay;

        delay = (tick_time->tv_sec - self->start_frame_time.tv_sec) * 1000 +
            (tick_time->tv_usec - self->start_frame_time.tv_usec) / 1000;
        if (delay < 0)
        {
            self->start_frame_time = *tick_time;
        }
        else if (delay != self->past_time)
        {
            self->past_time = delay;
            dtk_signal_emit(self, "new_frame", NULL);
            if (self->past_time > self->duration)
            {
                self->past_time = 0;                
                if (self->loop == DTK_TRUE)
                {
                    self->start_frame_time = *tick_time;
                }
                else
                {
                    self->waiting_first_tick = DTK_FALSE;
                    dtk_mc_remove_timeline(dtk_get_default_master_clock(), self);
                }
            }
        }
    }
    return; 
}
